import java.io.BufferedReader;
import java.util.Hashtable;
import java.util.LinkedList;

/**
*
* Ori Weisler  ID 060512878
* Rony Lahav   ID 043489889 
* Date: Jun 24, 2009
*/

public class SymTable {
	
	private static SymTable symTbl = null;
	private SymTable(){
		
	}
	
	public static SymTable getInstance(){
		if (symTbl == null ){
			symTbl = new SymTable();
		}
		return symTbl;
	}
	
	private static int memLoc = 0;	//next location in memory
	private static int symLoc = 0;		//next location in symbol table
	private static Hashtable<String, Table> symbolTableByName = new Hashtable<String, Table>();
	private static Hashtable<Integer, Table> symbolTableByLoc = new Hashtable<Integer, Table>(); 
	
	private static BufferedReader prog;	//for the JVM program content
	private static final String PROG_FILE_NAME = "prog.txt";
	
	private static LinkedList<Table> createIntSymTable(boolean isConst, String line){
		//for "int i=1,j;"
		LinkedList<Table> tableList = new LinkedList<Table>();
		Table table;
		String[] splittedVars = line.split(",");
		for (String var : splittedVars) {
			table = new Table();
			table.curLocation = symLoc;
			symLoc+=20;
			table.nextSymTable=symLoc;
			table.varType = "var";
			table.varData = 1002;
			if (!isConst){
				table.varMem = memLoc;
				memLoc+=4;
			}
			else
				table.varType="const";
			if (var.contains("=")){
				String[] values = var.split("=");
				table.varName = values[0].trim();
				if (isConst)
					table.varMem=Integer.parseInt(values[1].trim());
				else
					Compiler.memory[(table.varMem)/4]=Integer.parseInt(values[1].trim());
			}
			else
				table.varName = var.trim();
			tableList.add(table);
		}
		return tableList;
	}
	
	private static LinkedList<Table> createIntArraySymTable(boolean isConst, String line){
		//for "int a[10],b[5];"
		LinkedList<Table> tableList = new LinkedList<Table>();
		Table table;
		String[] splittedVars = line.split(",");
		for (String var : splittedVars) {
			table = new Table();
			table.curLocation = symLoc;
			symLoc+=20;
			String[] values = var.split("\\[");
			table.varName = values[0].trim();
			table.varType = "var";
			table.varData = symLoc;
			int length = Integer.parseInt(values[1].trim().substring(0, values[1].length()-1));
			//points to an array symbol
			Table arrayTable = createArraySymbolTable(table.varName, length);
			if (!isConst){
				table.varMem = memLoc;
				memLoc+=4*length;
			}
			else
				table.varType="const";
			table.nextSymTable=symLoc;	//was increased inside "createArraySymbolTable"
			tableList.add(table);
			tableList.add(arrayTable);
		}
		return tableList;
	}
	
	private static Table createArraySymbolTable(String varName, int length){
		//for array symbol of "a[10]"
		Table table = new Table();
		table.curLocation = symLoc;
		symLoc+=12;
		table.varName = "#" + varName;
		table.varType = "array";
		table.varData = 1002;
		table.varMem = length;		
		return table;
	}
	
	private static LinkedList<Table> createTypeSymTable(boolean isConst, String line){
		//for "z {int x;int y;} s,t" (for example)
		LinkedList<Table> tableList = new LinkedList<Table>();
		String[] data = line.split("\\{");
		String typeName = data[0].trim();
		data = data[1].split("\\}");
		String ints = data[0].trim();
		String[] vars=data[1].trim().split(",");
		Table table = new Table();
		table.curLocation = symLoc;
		symLoc+=20;
		table.varName=typeName;
		table.varType="type";
		table.varData=symLoc;
		//points to a struct symbol
		Table structTable = createTypeStructSymTable(typeName);
		//that points to a chain of ints
		LinkedList<Table> intChain = createTypeIntChain(ints, typeName);
		table.nextSymTable=symLoc;	//was increased inside "createTypeStructSymTable" and "createTypeIntChain"
		tableList.add(table);
		tableList.add(structTable);
		int jumpCount=0;
		for (Table intTable : intChain) {
			tableList.add(intTable);
			++jumpCount;
		}
		for (String var : vars) {
			Table varTable = creatTypeVarSymTable(isConst, var.trim(), table.varData, jumpCount);
			tableList.add(varTable);
		}
		return tableList;
	}
	
	private static Table createTypeStructSymTable(String typeName){
		//a struct symbol
		Table table = new Table();
		table.curLocation=symLoc;
		symLoc+=8;
		table.varName="#" + typeName;
		table.varType="struct";
		table.nextSymTable=symLoc;
		return table;
	}
	
	private static LinkedList<Table> createTypeIntChain(String ints, String father){
		//creates a chain of int fields for a struct
		LinkedList<Table> tableList = new LinkedList<Table>();
		Table table;
		String[] splittedVars = ints.split(";");
		for (int i=0;i<splittedVars.length;++i) {
			table = new Table();
			table.curLocation = symLoc;
			symLoc+=20;
			table.varName = splittedVars[i].trim().split(" ")[1] + "#" + father;
			table.varType="field";
			table.varData=1002;
			if (i+1 < splittedVars.length)	//there's another int in the chain
				table.nextSymTable=symLoc;
			tableList.add(table);
		}		
		return tableList;
	}
	
	private static Table creatTypeVarSymTable(boolean isConst, String varName, int varData, int jumpCount){
		//creates symbols for vars of type "type" 
		Table table = new Table();
		table.curLocation = symLoc;
		symLoc+=20;
		table.varName=varName;
		table.varType="var";
		table.varData=varData;
		if (!isConst){
			table.varMem=memLoc;
			memLoc+=4*jumpCount;
		}
		else
			table.varType="const";
		table.nextSymTable=symLoc;
		return table;
	}
	
	public boolean createNewSymTable (String line){
	    if (line.startsWith("int")){ //int
            if (line.contains("[")){    //int array
                LinkedList<Table> tableList = createIntArraySymTable(false, line.substring(4,line.length()-1)); //without "int " at beginning and ";" at end
                for (Table table : tableList) {
                    symbolTableByName.put(table.varName, table);
                    symbolTableByLoc.put(table.curLocation, table);
                }
                return true;
            }
            else{   //standard int
                LinkedList<Table> tableList = createIntSymTable(false, line.substring(4,line.length()-1));  //without "int " at beginning and ";" at end
                for (Table table : tableList) {
                    symbolTableByName.put(table.varName, table);
                    symbolTableByLoc.put(table.curLocation, table);
                }
                return true;
            }
        }
        else if (line.startsWith("struct")){ //struct
            LinkedList<Table> tableList = createTypeSymTable(false, line.substring(7,line.length()-1)); //without "struct " at beginning and ";" at end
            for (Table table : tableList) {
                symbolTableByName.put(table.varName, table);
                symbolTableByLoc.put(table.curLocation, table);
            }
            return true;
        }
        else if (line.startsWith("const")){   //const
            line = line.substring(6);   //without "const " at beginning
            if (line.startsWith("i")){  //int
                if (line.contains("[")){    //int array
                    LinkedList<Table> tableList = createIntArraySymTable(true, line.substring(4,line.length()-1));  //without "int " at beginning and ";" at end
                    for (Table table : tableList) {
                        symbolTableByName.put(table.varName, table);
                        symbolTableByLoc.put(table.curLocation, table);
                    }
                    return true;
                }
                else{   //standard int
                    LinkedList<Table> tableList = createIntSymTable(true, line.substring(4,line.length()-1));   //without "int " at beginning and ";" at end
                    for (Table table : tableList) {
                        symbolTableByName.put(table.varName, table);
                        symbolTableByLoc.put(table.curLocation, table);
                    }
                    return true;
                }
            }
            else if (line.startsWith("s")){ //struct
                LinkedList<Table> tableList = createTypeSymTable(true, line.substring(7,line.length()-1));  //without "struct " at beginning and ";" at end
                for (Table table : tableList) {
                    symbolTableByName.put(table.varName, table);
                    symbolTableByLoc.put(table.curLocation, table);
                }
                return true;
            }
        }
        else{   //no more declarations
          //create NULL reference for last symbol table
            Table nullTable = new Table();
            nullTable.curLocation=symLoc;
            nullTable.varName="NIL";
            symbolTableByName.put(nullTable.varName, nullTable);
            symbolTableByLoc.put(nullTable.curLocation, nullTable);
            return false;
        }
	    return false;
	}
	
	public void setVarValueByName (String varName, int value){
	    String struct, var;
	    Table table;
	    int memShift=-1;
	    if (varName.contains("#")){
	        String[] splitted = varName.split("#");
	        struct = splitted[0];
	        var = splitted[1];
	        table = symbolTableByName.get(struct);
	        do{
	            ++memShift;
	            table = symbolTableByLoc.get(table.varData);
	        }while (!table.varName.contains(var));
	        table = symbolTableByName.get(struct);
	    }
	    else{
	        table = symbolTableByName.get(varName);
	        memShift=0;
	    }
	    Compiler.memory[(table.varMem + memShift*4)/4]=value;
	}
	
	public void setVarValueByLocation (int loc, int memShift, int value){
	    Table table = symbolTableByLoc.get(loc);
	    Compiler.memory[(table.varMem + memShift*4)/4]=value;
	}
	
	public int getVarValueByName (String varName){
	    String struct, var;
        Table table;
        int memShift=-1;
        if (varName.contains("#")){
            String[] splitted = varName.split("#");
            struct = splitted[0];
            var = splitted[1];
            table = symbolTableByName.get(struct);
            do{
                ++memShift;
                table = symbolTableByLoc.get(table.varData);
            }while (!table.varName.contains(var));
            table = symbolTableByName.get(struct);
        }
        else{
            table = symbolTableByName.get(varName);
            memShift=0;
        }
        return Compiler.memory[(table.varMem + memShift*4)/4];
	}
	
	public int getVarValueByLocation (int loc, int memShift){
        Table table = symbolTableByLoc.get(loc);
        return Compiler.memory[(table.varMem + memShift*4)/4];
    }

	public static Table getTableByName (String name){
		if (name.contains("#")){	//struct
			String[] vars = name.split("#");
			String sName = vars[0];
			String iName = vars[1];
			Table table = symbolTableByName.get(sName);
			int mem = table.varMem;
			table = getTableByLoc(table.varData);
			table = getTableByLoc(table.nextSymTable);
			while (!table.varName.startsWith(iName)){
				mem+=4;
				table = getTableByLoc(table.nextSymTable);
			}
			table = symbolTableByName.get(sName).copy();
			table.varName = name;
			table.varMem=mem;
			return table;
		}
		else
			return symbolTableByName.get(name);
	}
	
	public static Table getTableByLoc (int loc){
		return symbolTableByLoc.get(loc);
	}
}
